package com.plata.base.aclr.utils;

import static android.telephony.TelephonyManager.NETWORK_TYPE_1xRTT;
import static android.telephony.TelephonyManager.NETWORK_TYPE_CDMA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_EDGE;
import static android.telephony.TelephonyManager.NETWORK_TYPE_EHRPD;
import static android.telephony.TelephonyManager.NETWORK_TYPE_EVDO_0;
import static android.telephony.TelephonyManager.NETWORK_TYPE_EVDO_A;
import static android.telephony.TelephonyManager.NETWORK_TYPE_EVDO_B;
import static android.telephony.TelephonyManager.NETWORK_TYPE_GPRS;
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSDPA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSPA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSPAP;
import static android.telephony.TelephonyManager.NETWORK_TYPE_HSUPA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_IDEN;
import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.text.TextUtils;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class NetworkUtils {
    private static final String TAG = "NetworkUtils";

    public static final ByteArrayPool sByteArrayPool = new ByteArrayPool(10240);

    public static final String NETWORK_CARD_NAME = "NETWORK_CARD_NAME";
    public static final String NETWORK_MAC_ADDRESS = "NETWORK_MAC_ADDRESS";
    public static final String NETWORK_IP_ADDRESS = "NETWORK_IP_ADDRESS";

    /**
     * MAC地址冒号分隔符
     */
    public static final String DELIMITER_COLON = ":";

    /**
     * MAC地址横线分隔符
     */
    public static final String DELIMITER_LINE = "-";

    /**
     * MAC地址无分隔符
     */
    public static final String DELIMITER_NULL = null;

    /**
     * 无线MAC
     */
    public static String WALN_MAC = "wlan0";

    /**
     * 有线MAC
     */
    public static String ETH_MAC = "eth0";

    private static final String NETWORK_NAME_NO_NETWORK = "no network";
    private static final String NETWORK_NAME_ETHERNET = "wired";
    private static final String NETWORK_NAME_PPPOE = "pppoe";
    private static final String NETWORK_NAME_WIFI = "wifi";
    private static final String NETWORK_NAME_2G = "2g";
    private static final String NETWORK_NAME_3G = "3g";
    private static final String NETWORK_NAME_4G = "4g";
    private static final String NETWORK_NAME_5G = "5g";
    private static final String NETWORK_NAME_MORE = "un";
    private static final String NETWORK_NAME_MOBILE = "mun";
    private static final String NETWORK_NAME_TD_SCDMA = "TD-SCDMA";
    private static final String NETWORK_NAME_WCDMA = "WCDMA";
    private static final String NETWORK_NAME_CDMA2000 = "CDMA2000";

    public static final int NETWORK_TYPE_NO_NETWORK = 0;
    public static final int NETWORK_TYPE_ETHERNET = 1;
    public static final int NETWORK_TYPE_MOBILE = 2;
    public static final int NETWORK_TYPE_WIFI = 3;
    public static final int NETWORK_TYPE_2G = 4;
    public static final int NETWORK_TYPE_3G = 5;
    public static final int NETWORK_TYPE_4G = 6;
    public static final int NETWORK_TYPE_5G = 7;
    public static final int NETWORK_TYPE_MORE = 8;

    public static void close(Closeable c) {
        try {
            c.close();
        } catch (Throwable e) {

        }
    }

    /**
     * Judge the network state.
     *
     * @param context
     * @return has connect
     */
    public static boolean hasConnect(Context context) {
        try {
            ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (null == connectivity) {
                return false;
            }
            @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo();
            return info == null || !info.isAvailable() || !info.isConnected() ? false : true;
        } catch (Exception e) {
            LogTool.e(TAG, "", e);
            return true;
        }
    }

    static Map<String, byte[]> nameMacMap = new HashMap<>();

    public static String getMac(String name) {
        byte[] b = getByteMac(name);
        if (b != null) {
            return bytesToHexWithDelimiter(b, ":");
        }
        return null;
    }

    public static byte[] getByteMac(String name) {
        if (nameMacMap.isEmpty()) {
            getNetworkInfo(":");
        }
        if (nameMacMap.isEmpty()) {
            return null;
        }
        if (nameMacMap.containsKey(name)) {
            return nameMacMap.get(name);
        } else {
            for (byte[] b : nameMacMap.values()) {
                return b;
            }
        }
        return null;
    }

    public static Map<String, String> getNetworkInfo(String delimiter) {
        Map<String, String> networkInfoMap = null;
        try {
            Enumeration<NetworkInterface> networkInterfaceEnum = NetworkInterface.getNetworkInterfaces();
            if (networkInterfaceEnum == null || !networkInterfaceEnum.hasMoreElements()) {
                return networkInfoMap;
            }

            networkInfoMap = new HashMap<String, String>();
            StringBuilder networkCardNameSb = new StringBuilder();
            StringBuilder networkIpAddressSb = new StringBuilder();
            StringBuilder networkMacAddressSb = new StringBuilder();

            byte[] macByte = null;
            NetworkInterface networkInterface = null;
            Enumeration<InetAddress> inetAddressEnum = null;
            InetAddress inetAddress = null;
            String networkCardName = null;
            String networkIpAddress = null;
            while (networkInterfaceEnum.hasMoreElements()) {
                networkInterface = networkInterfaceEnum.nextElement();
                networkCardName = networkInterface.getName();
                if (StringUtils.isEmpty(networkCardName)) {
                    continue;
                }
                inetAddressEnum = networkInterface.getInetAddresses();
                if (inetAddressEnum == null || !inetAddressEnum.hasMoreElements()) {
                    continue;
                }

                while (inetAddressEnum.hasMoreElements()) {
                    inetAddress = inetAddressEnum.nextElement();
                    if (inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) {
                        continue;
                    }

                    networkIpAddress = inetAddress.getHostAddress();
                    if (StringUtils.isEmpty(networkIpAddress)) {
                        continue;
                    }
                    if (nameMacMap.containsKey(networkCardName)) {
                        LogTool.i(TAG, "get mac from cache " + networkCardName);
                        macByte = nameMacMap.get(networkCardName);
                    } else {
                        LogTool.i(TAG, "get mac from interface " + networkCardName);
                        macByte = networkInterface.getHardwareAddress();
                        nameMacMap.put(networkCardName, macByte);
                        if (macByte == null || macByte.length <= 0) {
                            macByte = null;
                        }
                    }
                    String networkMacAddress = macByte == null ? null : bytesToHexWithDelimiter(macByte, delimiter);
                    LogTool.i(TAG, "getNetworkInfo. network card name(%s), network ip address(%s), network mac address(%s)", networkCardName, networkIpAddress, networkMacAddress);
                    if (networkCardNameSb.length() > 0) {
                        networkCardNameSb.append(",");
                    }
                    networkCardNameSb.append(networkCardName);
                    networkInfoMap.put(NETWORK_CARD_NAME, networkCardNameSb.toString());

                    if (networkIpAddressSb.length() > 0) {
                        networkIpAddressSb.append(",");
                    }
                    networkIpAddressSb.append(networkIpAddress);
                    networkInfoMap.put(NETWORK_IP_ADDRESS, networkIpAddressSb.toString());

                    if (networkMacAddressSb.length() > 0) {
                        networkMacAddressSb.append(",");
                    }
                    networkMacAddressSb.append(networkMacAddress == null ? "" : networkMacAddress);
                    networkInfoMap.put(NETWORK_MAC_ADDRESS, networkMacAddressSb.toString());
                }
            }
        } catch (Exception e) {
            LogTool.e(TAG, "", e);
        }
        return networkInfoMap;
    }

    /**
     * 转化成分割形式
     *
     * @param b
     * @return
     */
    @SuppressLint("DefaultLocale")
    private static String bytesToHexWithDelimiter(byte[] b, String delimiter) {
        if (null == b) {
            return null;
        }
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int i = 0; i < b.length; i++) {
            if (!StringUtils.isEmpty(delimiter) && hs.length() > 0) {
                hs.append(delimiter);
            }
            stmp = (Integer.toHexString(b[i] & 0XFF));
            if (1 == stmp.length()) {
                hs.append("0").append(stmp);
            } else {
                hs.append(stmp);
            }
        }

        return hs.toString().toUpperCase();
    }

    private NetworkUtils() {
    }

    /**
     * ByteArrayPool is a source and repository of <code>byte[]</code> objects. Its purpose is to
     * supply those buffers to consumers who need to use them for a short period of time and then
     * dispose of them. Simply creating and disposing such buffers in the conventional manner can
     * considerable heap churn and garbage collection delays on Android, which lacks good management of
     * short-lived heap objects. It may be advantageous to trade off some memory in the form of a
     * permanently allocated pool of buffers in order to gain heap performance improvements; that is
     * what this class does.
     * <p>
     * A good candidate user for this class is something like an I/O system that uses large temporary
     * <code>byte[]</code> buffers to copy data around. In these use cases, often the consumer wants
     * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks
     * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into
     * account and also to maximize the odds of being able to reuse a recycled buffer, this class is
     * free to return buffers larger than the requested size. The caller needs to be able to gracefully
     * deal with getting buffers any size over the minimum.
     * <p>
     * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this
     * class will allocate a new buffer and return it.
     * <p>
     * This class has no special ownership of buffers it creates; the caller is free to take a buffer
     * it receives from this pool, use it permanently, and never return it to the pool; additionally,
     * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there
     * are no other lingering references to it.
     * <p>
     * This class ensures that the total size of the buffers in its recycling pool never exceeds a
     * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit,
     * least-recently-used buffers are disposed.
     */
    public static class ByteArrayPool {
        /**
         * The buffer pool, arranged both by last use and by buffer size
         */
        private final List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();
        private final List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);

        /**
         * The total size of the buffers in the pool
         */
        private int mCurrentSize = 0;

        /**
         * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay
         * under this limit.
         */
        private final int mSizeLimit;

        /**
         * Compares buffers by size
         */
        protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
            @Override
            public int compare(byte[] lhs, byte[] rhs) {
                return lhs.length - rhs.length;
            }
        };

        /**
         * @param sizeLimit the maximum size of the pool, in bytes
         */
        public ByteArrayPool(int sizeLimit) {
            this.mSizeLimit = sizeLimit;
        }

        /**
         * Returns a buffer from the pool if one is available in the requested size, or allocates a new
         * one if a pooled one is not available.
         *
         * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be
         *            larger.
         * @return a byte[] buffer is always returned.
         */
        public synchronized byte[] getBuf(int len) {
            for (int i = 0; i < this.mBuffersBySize.size(); i++) {
                byte[] buf = this.mBuffersBySize.get(i);
                if (buf.length >= len) {
                    this.mCurrentSize -= buf.length;
                    this.mBuffersBySize.remove(i);
                    this.mBuffersByLastUse.remove(buf);
                    return buf;
                }
            }
            return new byte[len];
        }

        /**
         * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
         * size.
         *
         * @param buf the buffer to return to the pool.
         */
        public synchronized void returnBuf(byte[] buf) {
            if (buf == null || buf.length > this.mSizeLimit) {
                return;
            }
            this.mBuffersByLastUse.add(buf);
            int pos = Collections.binarySearch(this.mBuffersBySize, buf, BUF_COMPARATOR);
            if (pos < 0) {
                pos = -pos - 1;
            }
            this.mBuffersBySize.add(pos, buf);
            this.mCurrentSize += buf.length;
            this.trim();
        }

        /**
         * Removes buffers from the pool until it is under its size limit.
         */
        private synchronized void trim() {
            while (this.mCurrentSize > this.mSizeLimit) {
                byte[] buf = this.mBuffersByLastUse.remove(0);
                this.mBuffersBySize.remove(buf);
                this.mCurrentSize -= buf.length;
            }
        }
    }

    /**
     * A variation of {@link ByteArrayOutputStream} that uses a pool of byte[] buffers instead
     * of always allocating them fresh, saving on heap churn.
     */
    public static class PoolingByteArrayOutputStream extends ByteArrayOutputStream {
        /**
         * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor is called, this is
         * the default size to which the underlying byte array is initialized.
         */
        private static final int DEFAULT_SIZE = 256;

        private final ByteArrayPool mPool;

        /**
         * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written
         * to this instance, the underlying byte array will expand.
         */
        public PoolingByteArrayOutputStream(ByteArrayPool pool) {
            this(pool, DEFAULT_SIZE);
        }

        /**
         * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If
         * more than {@code size} bytes are written to this instance, the underlying byte array will
         * expand.
         *
         * @param size initial size for the underlying byte array. The value will be pinned to a default
         *             minimum size.
         */
        public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
            this.mPool = pool;
            this.buf = this.mPool.getBuf(Math.max(size, DEFAULT_SIZE));
        }

        @Override
        public void close() throws IOException {
            this.mPool.returnBuf(this.buf);
            this.buf = null;
            super.close();
        }

        @Override
        public void finalize() {
            this.mPool.returnBuf(this.buf);
        }

        /**
         * Ensures there is enough space in the buffer for the given number of additional bytes.
         */
        private void expand(int i) {
            /* Can the buffer handle @i more bytes, if not expand it */
            if (this.count + i <= this.buf.length) {
                return;
            }
            byte[] newbuf = this.mPool.getBuf((this.count + i) * 2);
            System.arraycopy(this.buf, 0, newbuf, 0, this.count);
            this.mPool.returnBuf(this.buf);
            this.buf = newbuf;
        }

        @Override
        public synchronized void write(byte[] buffer, int offset, int len) {
            this.expand(len);
            super.write(buffer, offset, len);
        }

        @Override
        public synchronized void write(int oneByte) {
            this.expand(1);
            super.write(oneByte);
        }
    }

    public static String getNetworkName(Context context) {
        int networkClass = getNetworkType(context);
        return getNetworkName(networkClass);
    }

    public static String getNetworkName(int networkClass) {
        String type = NETWORK_NAME_MORE;
        switch (networkClass) {
            case NETWORK_TYPE_NO_NETWORK:
                type = NETWORK_NAME_NO_NETWORK;
                break;
            case NETWORK_TYPE_WIFI:
                type = NETWORK_NAME_WIFI;
                break;
            case NETWORK_TYPE_2G:
                type = NETWORK_NAME_2G;
                break;
            case NETWORK_TYPE_3G:
                type = NETWORK_NAME_3G;
                break;
            case NETWORK_TYPE_4G:
                type = NETWORK_NAME_4G;
                break;
            case NETWORK_TYPE_5G:
                type = NETWORK_NAME_5G;
                break;
            case NETWORK_TYPE_MORE:
                type = NETWORK_NAME_MORE;
                break;
            case NETWORK_TYPE_ETHERNET:
                type = NETWORK_NAME_ETHERNET;
                break;
        }
        return type;
    }

    private static int getNetworkClassByType(int networkType) {
        switch (networkType) {
            case NETWORK_TYPE_GPRS:
            case NETWORK_TYPE_EDGE:
            case NETWORK_TYPE_CDMA:
            case NETWORK_TYPE_1xRTT:
            case NETWORK_TYPE_IDEN:
                return NETWORK_TYPE_2G;
            case NETWORK_TYPE_UMTS:
            case NETWORK_TYPE_EVDO_0:
            case NETWORK_TYPE_EVDO_A:
            case NETWORK_TYPE_HSDPA:
            case NETWORK_TYPE_HSUPA:
            case NETWORK_TYPE_HSPA:
            case NETWORK_TYPE_EVDO_B:
            case NETWORK_TYPE_EHRPD:
            case NETWORK_TYPE_HSPAP:
                return NETWORK_TYPE_3G;
            case NETWORK_TYPE_LTE:
            case 20:
                return NETWORK_TYPE_4G;
//          treat 5G as 4G
//            case 20: // NETWORK_TYPE_NR, api 29
//                return NETWORK_TYPE_5G;
            default:
                return NETWORK_TYPE_MORE;
        }
    }

    @SuppressLint("MissingPermission")
    public static int getNetworkType(Context context) {
        try {
            ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            final NetworkInfo network = connectivity.getActiveNetworkInfo();
            if (network != null && network.isAvailable()
                    && network.isConnected()) {
                int type = network.getType();
                if (type == ConnectivityManager.TYPE_WIFI) {
                    return NETWORK_TYPE_WIFI;
                } else if (type == ConnectivityManager.TYPE_MOBILE) {
                    int networkType = network.getSubtype();
                    return getNetworkClassByType(networkType);
                } else if (type == ConnectivityManager.TYPE_ETHERNET) {
                    return NETWORK_TYPE_ETHERNET;
                }
            } else {
                return NETWORK_TYPE_NO_NETWORK;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return NETWORK_TYPE_MORE;
    }

    public static boolean isWifiProxy(Context mContext) {
        try {
            final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
            String proxyAddress;
            int proxyPort;
            if (IS_ICS_OR_LATER) {
                proxyAddress = System.getProperty("http.proxyHost");
                String portStr = System.getProperty("http.proxyPort");
                proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
            } else {
                proxyAddress = android.net.Proxy.getHost(mContext);
                proxyPort = android.net.Proxy.getPort(mContext);
            }
            return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
        } catch (Throwable e) {
            return false;
        }
    }

    public static boolean isWifi(Context var0) {
        if (isRejectAccessNetworkState(var0)) {
            return false;
        } else {
            NetworkInfo var1;
            if ((var1 = getActiveNetworkInfo(var0)) != null && var1.isConnected()) {
                return var1.getType() == 1;
            } else {
                return false;
            }
        }
    }

    public static boolean hasAccessNetworkState(Context var0) {
        try {
            return var0.getPackageManager().checkPermission("android.permission.ACCESS_NETWORK_STATE", var0.getPackageName()) == PackageManager.PERMISSION_GRANTED;
        } catch (Throwable var1) {
            return false;
        }
    }

    public static boolean isRejectAccessNetworkState(Context var0) {
        return !hasAccessNetworkState(var0);
    }

    public static NetworkInfo getActiveNetworkInfo(Context var0) {
        NetworkInfo var1 = null;

        try {
            ConnectivityManager var4;
            if ((var4 = (ConnectivityManager) var0.getSystemService(Context.CONNECTIVITY_SERVICE)) == null) {
                return null;
            }

            NetworkInfo[] var5;
            if (((var1 = var4.getActiveNetworkInfo()) == null || !var1.isConnected()) && (var5 = var4.getAllNetworkInfo()) != null) {
                for (int var2 = 0; var2 < var5.length; ++var2) {
                    if (var5[var2] != null && var5[var2].isConnected()) {
                        var1 = var5[var2];
                        break;
                    }
                }
            }
        } catch (Throwable var3) {
        }

        return var1;
    }
}
